package alibaba.server;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ronenkat
 *
 */
public class Listing {
	public static class OwnerSnapshotPair {
		private String owner;
		private int snapshotNumber;

		public OwnerSnapshotPair() {
		}

		public OwnerSnapshotPair(String owner, int snapshotNumber) {
			this.owner = owner;
			this.snapshotNumber = snapshotNumber;
		}

		public String getOwner() {
			return owner;
		}

		public void setOwner(String owner) {
			this.owner = owner;
		}

		public int getSnapshotNumber() {
			return snapshotNumber;
		}

		public void setSnapshotNumber(int snapshotNumber) {
			this.snapshotNumber = snapshotNumber;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((owner == null) ? 0 : owner.hashCode());
			result = prime * result + snapshotNumber;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			OwnerSnapshotPair other = (OwnerSnapshotPair) obj;
			if (owner == null) {
				if (other.owner != null)
					return false;
			} else if (!owner.equals(other.owner))
				return false;
			if (snapshotNumber != other.snapshotNumber)
				return false;
			return true;
		}
	}

	public static final int CURRENT_SNAPSHOT = 0;

	private Map<String, List<Integer>> ownerToSnapshotsMap;
	private Map<OwnerSnapshotPair, Map<String, ListingEntry>> listOfFiles;

	/**
	 * @param listOfFiles
	 */
	public Listing(Map<String, List<Integer>> ownerToSnapshotsMap,
			Map<OwnerSnapshotPair, Map<String, ListingEntry>> listOfFiles) {
		super();
		this.ownerToSnapshotsMap = ownerToSnapshotsMap;
		this.listOfFiles = listOfFiles;
	}

	/**
	 */
	public Listing() {
		super();
		this.ownerToSnapshotsMap = new HashMap<String, List<Integer>>();
		this.listOfFiles = new HashMap<OwnerSnapshotPair, Map<String, ListingEntry>>();
	}

	/**
	 * @return the listOfFiles
	 */
	public Map<OwnerSnapshotPair, Map<String, ListingEntry>> getListOfFiles() {
		return listOfFiles;
	}

	/**
	 * @param listOfFiles
	 *            the listOfFiles to set
	 */
	public void setListOfFiles(Map<OwnerSnapshotPair, Map<String, ListingEntry>> listOfFiles) {
		this.listOfFiles = listOfFiles;
	}


	public ListingEntry getFileEntry(String owner, int snapshotNumber, String filename) {
		Map<String, ListingEntry> map = this.listOfFiles.get(new OwnerSnapshotPair(owner, snapshotNumber));
		if (map == null) {
			return null;
		}
 		return this.listOfFiles.get(new OwnerSnapshotPair(owner, snapshotNumber)).get(filename);
	}

	public Map<String, ListingEntry> getFilesForOwnerSnpashot(String owner, int snapshotNumber) {
		OwnerSnapshotPair ownerSnapshot = new OwnerSnapshotPair(owner, snapshotNumber);
		if (!this.listOfFiles.containsKey(ownerSnapshot)) {
			this.listOfFiles.put(ownerSnapshot, new HashMap<String, ListingEntry>());
		}
		return this.listOfFiles.get(ownerSnapshot);
	}

	public boolean createSnapshot(String owner, int snapshotNumber) {
		if (this.ownerToSnapshotsMap.get(owner) == null) {
			this.ownerToSnapshotsMap.put(owner, new ArrayList<Integer>(Arrays.asList(0)));
		}
		List<Integer> snapshotsList = this.ownerToSnapshotsMap.get(owner);
		if (snapshotsList.contains(snapshotNumber)) {
			return false;
		}
		snapshotsList.add(snapshotNumber);
		for (ListingEntry listingEntry : getFilesForOwnerSnpashot(owner, CURRENT_SNAPSHOT).values()) {
			ListingEntry copy = listingEntry.clone();
			copy.setSnapshotVersion(snapshotNumber);
			getFilesForOwnerSnpashot(owner, snapshotNumber).put(copy.getFilename(), copy);
		}
		return true;
	}

	public boolean deleteSnapshot(String owner, int snapshotNumber) {
		if ((this.ownerToSnapshotsMap.get(owner) == null) ||
				!this.ownerToSnapshotsMap.get(owner).remove(Integer.valueOf(snapshotNumber))) {
			return false;
		}
		this.listOfFiles.remove(new OwnerSnapshotPair(owner, snapshotNumber));
		return true;
	}

	/**
	 * Adds an entry to the listing.
	 *
	 * The entry is always added to the default snapshot, overriding any other snapshot set in the
	 * entry.
	 *
	 * @param entry
	 */
	public void addFileEntry(ListingEntry entry) {
		entry.setSnapshotVersion(CURRENT_SNAPSHOT);
		getFilesForOwnerSnpashot(entry.getOwner(), CURRENT_SNAPSHOT).put(entry.getFilename(), entry);
	}

	/**
	 * Write the backup server meta information repository to disk
	 *
	 * @param filename
	 * @param listing
	 * @throws FileNotFoundException
	 */
	public static void writeListing(String filename, Listing listing) throws FileNotFoundException  {
		XMLEncoder e = new XMLEncoder(new BufferedOutputStream(new FileOutputStream(filename)));
		e.writeObject(listing);
		e.close();
	}

	/**
	 * Read the backup server meta information repository from disk
	 *
	 * @param filename
	 * @return
	 * @throws FileNotFoundException
	 */
	public static Listing readListing(String filename) throws FileNotFoundException {
		XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream(filename)));
		Object o = d.readObject();
		if (o instanceof Listing) {
			return (Listing) o;
		}
		return null;
	}

	public void setOwnerToSnapshotsMap(Map<String, List<Integer>> ownerToSnapshotsMap) {
		this.ownerToSnapshotsMap = ownerToSnapshotsMap;
	}

	public Map<String, List<Integer>> getOwnerToSnapshotsMap() {
		return ownerToSnapshotsMap;
	}
}
